Skip to content

Release 2.5.0 - Add Stats API#63

Open
piobeny wants to merge 2 commits intomainfrom
release-2.5.0
Open

Release 2.5.0 - Add Stats API#63
piobeny wants to merge 2 commits intomainfrom
release-2.5.0

Conversation

@piobeny
Copy link

@piobeny piobeny commented Mar 9, 2026

Motivation

  • Bump version to 2.5.0

Changes

  • Bump version to 2.5.0

How to test

N/A

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds a new StatsApi to fetch and aggregate sending statistics (multiple endpoints and grouped responses), new stats models and filter params with automatic list query serialization, an example script, unit tests, and bumps project version to 2.5.0.

Changes

Cohort / File(s) Summary
Version & Changelog
pyproject.toml, CHANGELOG.md
Bump version to 2.5.0 and update docs URL; add CHANGELOG entry for StatsApi and list-param serialization.
API surface
mailtrap/api/resources/stats.py, mailtrap/api/general.py
Add StatsApi with methods get, by_domains, by_categories, by_email_service_providers, by_date; expose GeneralApi.stats property.
Models & params
mailtrap/models/stats.py, mailtrap/models/common.py
Add SendingStats, SendingStatGroup, and StatsFilterParams; add RequestParams.api_query_params property that serializes list values with key[].
Package exports
mailtrap/__init__.py
Re-export StatsFilterParams for public SDK usage.
Examples
examples/general/stats.py
New example demonstrating all stats endpoints and filter usage.
Tests
tests/unit/api/general/test_stats.py
Comprehensive tests covering success cases, grouped responses, parameter serialization, and HTTP error handling.
Other
manifest_file, requirements.txt
Manifest and requirements changes recorded (no functional code changes described).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant MailtrapClient as MailtrapClient
    participant GeneralApi as GeneralApi
    participant StatsApi as StatsApi
    participant HttpClient as HttpClient
    participant MtApi as Mailtrap API

    Client->>MailtrapClient: initialize(api_token)
    Client->>MailtrapClient: access general_api.stats
    MailtrapClient->>GeneralApi: request stats property
    GeneralApi->>StatsApi: create/return StatsApi

    Client->>StatsApi: get(account_id, params)
    activate StatsApi
    StatsApi->>StatsApi: _base_path(account_id)
    StatsApi->>HttpClient: GET /api/accounts/{id}/stats?params
    deactivate StatsApi
    HttpClient->>MtApi: perform HTTP GET
    MtApi-->>HttpClient: SendingStats JSON
    HttpClient-->>StatsApi: parsed JSON
    StatsApi->>StatsApi: construct SendingStats(...)
    StatsApi-->>Client: SendingStats

    Client->>StatsApi: by_domains(account_id, params)
    activate StatsApi
    StatsApi->>HttpClient: GET /api/accounts/{id}/stats/domains?params
    deactivate StatsApi
    HttpClient->>MtApi: perform HTTP GET
    MtApi-->>HttpClient: grouped JSON array
    HttpClient-->>StatsApi: parsed JSON
    loop per item
        StatsApi->>StatsApi: map item -> SendingStatGroup(name, value, stats)
    end
    StatsApi-->>Client: list[SendingStatGroup]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • VladimirTaytor
  • andrii-porokhnavets
  • yanchuk
  • IgorDobryn

Poem

🐰 I hopped through code with fluffy paws,

added stats and tidy clause.
Brackets bloom on listy trails,
domains and dates tell their tales.
Cheers — a carrot for new API rails! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description lacks detailed information about the substantial changes introduced, including new StatsApi, models, and examples. Expand the description to document all key changes: new StatsApi class, SendingStats/SendingStatGroup/StatsFilterParams models, api_query_params property, example file, and updated documentation.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Release 2.5.0 - Add Stats API' directly summarizes the main change: introducing the Stats API as part of version 2.5.0.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch release-2.5.0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/unit/api/general/test_stats.py (1)

165-169: Assert the serialized list values, not just the key names.

These checks still pass if the request encodes each list under the right key but with the wrong shape. Since this PR adds list-param serialization, it would be better to assert the exact repeated values in the query string.

Proposed test tightening
+from urllib.parse import parse_qs
+from urllib.parse import urlparse
+
 from typing import Any
 
 import pytest
 import responses
@@
-        request_params = responses.calls[0].request.params
-        assert "sending_domain_ids[]" in request_params
-        assert "sending_streams[]" in request_params
-        assert "categories[]" in request_params
-        assert "email_service_providers[]" in request_params
+        request_params = parse_qs(urlparse(responses.calls[0].request.url).query)
+        assert request_params["sending_domain_ids[]"] == ["1", "2"]
+        assert request_params["sending_streams[]"] == ["transactional"]
+        assert request_params["categories[]"] == ["Transactional"]
+        assert request_params["email_service_providers[]"] == ["Gmail"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/general/test_stats.py` around lines 165 - 169, Update the
assertions in the test that inspect responses.calls[0].request.params so they
assert the serialized list values (not just the presence of keys); for example,
replace the four assert "key in request_params" checks with assertions that
request_params["sending_domain_ids[]"], request_params["sending_streams[]"],
request_params["categories[]"], and request_params["email_service_providers[]"]
equal the expected lists (or expected repeated values) used in the test input —
this ensures the serialized shape and values produced by the list-param
serialization logic (as observed via request_params) are exactly what the call
should send.
mailtrap/api/resources/stats.py (3)

1-4: Consider consolidating imports from the same module.

The three imports from mailtrap.models.stats can be combined into a single statement for cleaner code.

♻️ Suggested consolidation
 from mailtrap.http import HttpClient
-from mailtrap.models.stats import SendingStatGroup
-from mailtrap.models.stats import SendingStats
-from mailtrap.models.stats import StatsFilterParams
+from mailtrap.models.stats import SendingStatGroup, SendingStats, StatsFilterParams
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mailtrap/api/resources/stats.py` around lines 1 - 4, Consolidate the three
separate imports from mailtrap.models.stats into a single import statement:
replace the individual imports of SendingStatGroup, SendingStats, and
StatsFilterParams with one grouped import from mailtrap.models.stats so the top
of the file imports HttpClient from mailtrap.http and imports SendingStatGroup,
SendingStats, StatsFilterParams together from mailtrap.models.stats.

6-11: Consider making GROUP_KEYS a private constant.

Since GROUP_KEYS is an internal implementation detail used only by _grouped_stats, prefixing it with an underscore (_GROUP_KEYS) would signal this intent and prevent external reliance on the mapping.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mailtrap/api/resources/stats.py` around lines 6 - 11, Rename the public
constant GROUP_KEYS to a private constant named _GROUP_KEYS and update all
references to it (notably inside the _grouped_stats function) so the
implementation detail is not exposed; ensure any import/usage within the same
module uses _GROUP_KEYS and run tests to confirm there are no external
references that need updating.

50-65: Consider stricter typing for the group parameter.

The group parameter only accepts values from GROUP_KEYS. Using a Literal type would provide better type safety and IDE support.

♻️ Suggested type improvement
+from typing import Literal
+
+GroupType = Literal["domains", "categories", "email_service_providers", "date"]
+
     def _grouped_stats(
-        self, account_id: int, group: str, params: StatsFilterParams
+        self, account_id: int, group: GroupType, params: StatsFilterParams
     ) -> list[SendingStatGroup]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mailtrap/api/resources/stats.py` around lines 50 - 65, The group parameter to
_grouped_stats only accepts keys from GROUP_KEYS, so declare a stricter Literal
alias (e.g. GroupKey = Literal['keyA', 'keyB', ...] where the string literals
enumerate the keys in GROUP_KEYS), replace the method signature to def
_grouped_stats(self, account_id: int, group: GroupKey, params:
StatsFilterParams) -> list[SendingStatGroup], and update any callers/type hints
accordingly; keep the runtime behavior unchanged (still use GROUP_KEYS[group]
and response parsing into SendingStatGroup).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/general/stats.py`:
- Line 7: ACCOUNT_ID is declared as a string but passed to helper functions
typed to accept int; change the declaration of ACCOUNT_ID from a string to an
integer literal (e.g., ACCOUNT_ID = 123456) and update any other sample
declarations/usages (the block around the later occurrences) so they pass an int
to the helper functions referenced in this file; ensure references to ACCOUNT_ID
in functions like the stats helpers now receive an int to keep types consistent.

---

Nitpick comments:
In `@mailtrap/api/resources/stats.py`:
- Around line 1-4: Consolidate the three separate imports from
mailtrap.models.stats into a single import statement: replace the individual
imports of SendingStatGroup, SendingStats, and StatsFilterParams with one
grouped import from mailtrap.models.stats so the top of the file imports
HttpClient from mailtrap.http and imports SendingStatGroup, SendingStats,
StatsFilterParams together from mailtrap.models.stats.
- Around line 6-11: Rename the public constant GROUP_KEYS to a private constant
named _GROUP_KEYS and update all references to it (notably inside the
_grouped_stats function) so the implementation detail is not exposed; ensure any
import/usage within the same module uses _GROUP_KEYS and run tests to confirm
there are no external references that need updating.
- Around line 50-65: The group parameter to _grouped_stats only accepts keys
from GROUP_KEYS, so declare a stricter Literal alias (e.g. GroupKey =
Literal['keyA', 'keyB', ...] where the string literals enumerate the keys in
GROUP_KEYS), replace the method signature to def _grouped_stats(self,
account_id: int, group: GroupKey, params: StatsFilterParams) ->
list[SendingStatGroup], and update any callers/type hints accordingly; keep the
runtime behavior unchanged (still use GROUP_KEYS[group] and response parsing
into SendingStatGroup).

In `@tests/unit/api/general/test_stats.py`:
- Around line 165-169: Update the assertions in the test that inspect
responses.calls[0].request.params so they assert the serialized list values (not
just the presence of keys); for example, replace the four assert "key in
request_params" checks with assertions that
request_params["sending_domain_ids[]"], request_params["sending_streams[]"],
request_params["categories[]"], and request_params["email_service_providers[]"]
equal the expected lists (or expected repeated values) used in the test input —
this ensures the serialized shape and values produced by the list-param
serialization logic (as observed via request_params) are exactly what the call
should send.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae6b307f-d0d4-4d77-8389-241e8f13796b

📥 Commits

Reviewing files that changed from the base of the PR and between 8821498 and a8a254b.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • examples/general/stats.py
  • mailtrap/__init__.py
  • mailtrap/api/general.py
  • mailtrap/api/resources/stats.py
  • mailtrap/models/common.py
  • mailtrap/models/stats.py
  • pyproject.toml
  • tests/unit/api/general/test_stats.py

Documentation = "https://github.com/railsware/mailtrap-python"
Repository = "https://github.com/railsware/mailtrap-python.git"
"API documentation" = "https://api-docs.mailtrap.io/"
"API documentation" = "https://docs.mailtrap.io/developers"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
examples/general/stats.py (2)

11-55: Avoid baking January 2026 into every sample call.

This example will look stale quickly and often return empty data unless the reader happens to have stats in that exact window. A shared START_DATE / END_DATE placeholder pair would keep the sample easier to update.

Possible cleanup
+START_DATE = "YYYY-MM-DD"
+END_DATE = "YYYY-MM-DD"
+
 def get_stats(account_id: int) -> SendingStats:
-    params = StatsFilterParams(start_date="2026-01-01", end_date="2026-01-31")
+    params = StatsFilterParams(start_date=START_DATE, end_date=END_DATE)

Apply the same replacement to the other helpers in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/general/stats.py` around lines 11 - 55, The examples hard-code the
January 2026 date range in every helper (e.g., get_stats, get_stats_by_domains,
get_stats_by_categories, get_stats_by_email_service_providers,
get_stats_by_date, get_stats_with_filters, get_stats_by_domains_with_filters);
replace those literal start_date/end_date strings with shared constants (e.g.,
START_DATE and END_DATE) defined once at the top of the module and use them in
all StatsFilterParams instances so the sample is easy to update in one place and
avoid stale/empty results.

1-8: Load the sample token from the environment instead of source.

A literal placeholder keeps secret scanners noisy and nudges copy-paste users toward editing credentials into the example.

Possible cleanup
+import os
+
 import mailtrap as mt
 from mailtrap.models.stats import SendingStatGroup, SendingStats, StatsFilterParams
 
-API_TOKEN = "YOUR_API_TOKEN"
+API_TOKEN = os.environ["MAILTRAP_API_TOKEN"]
 ACCOUNT_ID = 123456
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/general/stats.py` around lines 1 - 8, Replace the hard-coded
API_TOKEN and ACCOUNT_ID in examples/general/stats.py with environment
variables: import os, read API_TOKEN via os.getenv('MAILTRAP_API_TOKEN') and
ACCOUNT_ID via os.getenv('MAILTRAP_ACCOUNT_ID') (convert to int as needed), and
keep a clear non-secret fallback or guard that raises a helpful error if the env
var is missing before constructing MailtrapClient; update references to
API_TOKEN and ACCOUNT_ID accordingly (symbols: API_TOKEN, ACCOUNT_ID,
MailtrapClient, stats_api).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@examples/general/stats.py`:
- Around line 11-55: The examples hard-code the January 2026 date range in every
helper (e.g., get_stats, get_stats_by_domains, get_stats_by_categories,
get_stats_by_email_service_providers, get_stats_by_date, get_stats_with_filters,
get_stats_by_domains_with_filters); replace those literal start_date/end_date
strings with shared constants (e.g., START_DATE and END_DATE) defined once at
the top of the module and use them in all StatsFilterParams instances so the
sample is easy to update in one place and avoid stale/empty results.
- Around line 1-8: Replace the hard-coded API_TOKEN and ACCOUNT_ID in
examples/general/stats.py with environment variables: import os, read API_TOKEN
via os.getenv('MAILTRAP_API_TOKEN') and ACCOUNT_ID via
os.getenv('MAILTRAP_ACCOUNT_ID') (convert to int as needed), and keep a clear
non-secret fallback or guard that raises a helpful error if the env var is
missing before constructing MailtrapClient; update references to API_TOKEN and
ACCOUNT_ID accordingly (symbols: API_TOKEN, ACCOUNT_ID, MailtrapClient,
stats_api).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56f1334c-0854-40b5-a3f1-6b65a2ff5b92

📥 Commits

Reviewing files that changed from the base of the PR and between a8a254b and c4a1e5e.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • examples/general/stats.py
  • mailtrap/api/resources/stats.py
  • mailtrap/models/stats.py
  • pyproject.toml
  • tests/unit/api/general/test_stats.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/unit/api/general/test_stats.py
  • pyproject.toml
  • mailtrap/models/stats.py
  • CHANGELOG.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants